import numpy as np

"""
热身： numpy

在介绍 PyTorch 之前，我们首先使用 numpy 来实现这个神经网络。
numpy 提供了一个 n 维数组对象和许多操作这些数组的函数。
numpy 是一个通用的科学计算框架；它没有计算图、深度学习和梯度的概念。
然而我们可以很容易的使用 numpy 的操作手动实现前向传播和反向传播让一个两层的网络来拟合随机数据：
"""

# N 批的大小
# D_in 输入的数据纬度
# H 隐藏层纬度
# D_out 输出数据纬度
# N is batch size; D_in is input dimension;
# H is hidden dimension; D_out is output dimension.
N, D_in, H, D_out = 1, 2, 2, 1
print("N", N)
print("D_in", D_in)
print("H", H)
print("D_out", D_out)

# 创建随机的输入和输出数据纬度
# Create random input and output data
x = np.random.randn(N, D_in)
print("x", x)
y = np.random.randn(N, D_out)
print("y", y)

# 随机初始化权重w的数据，权重在0左右之间，呈正态分布
# Randomly initialize weights
w1 = np.random.randn(D_in, H)
print("w1", w1)
w2 = np.random.randn(H, D_out)
print("w2", w2)

# 设置学习率
learning_rate = 1e-2
print("learning_rate", learning_rate)

# 开始学习
for t in range(1000):
    # 正向传播: 计算预测值 y
    # Forward pass: compute predicted y
    # print("x", x)
    # 进行线性变换wx
    h = x.dot(w1)
    # print("h", h)
    # 进行非线性变换
    h_relu = np.maximum(h, 0)
    # print("h_relu", h_relu)
    # 进行线性计算
    y_pred = h_relu.dot(w2)
    # print("y_pred", y_pred)

    # 计算和打印损失，损失是误差的平方和
    # Compute and print loss
    loss = np.square(y_pred - y).sum()
    print("loss", loss)

    # 向后传播计算w1和w2对于误差的梯度
    # Backprop to compute gradients of w1 and w2 with respect to loss
    # 先计算w2的梯度
    # 计算误差
    grad_y_pred = 2.0 * (y_pred - y)
    # print("grad_y_pred", grad_y_pred)
    # 转置[[1 2]]=>[[1],[2]]
    # print(" h_relu.T", h_relu.T)
    # 根据误差计算w2的梯度，非线性变换的转置点积误差
    grad_w2 = h_relu.T.dot(grad_y_pred)
    # print("grad_w2", grad_w2)
    # 其非线性变换的梯度，用于向后传播
    grad_h_relu = grad_y_pred.dot(w2.T)
    # print("grad_h_relu", grad_h_relu)
    grad_h = grad_h_relu.copy()
    # print("grad_h", grad_h)
    grad_h_relu[h < 0] = 0
    # 根据传递过来的误差求w1的梯度
    grad_w1 = x.T.dot(grad_h)
    # print("grad_w1", grad_w1)

    # 更新权重
    # update weights
    w1 -= learning_rate * grad_w1
    w2 -= learning_rate * grad_w2
    print("w1", "w2", w1, w2)

print("y_pred", y_pred)
